/*
 * This file is part of Cadmium.
 * Copyright (C) 2007-2010 Xavier Clerc.
 *
 * Cadmium is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * Cadmium is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package fr.x9c.cadmium.gui;

import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Frame;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.Box;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.JTextField;
import javax.swing.SpinnerNumberModel;
import javax.swing.SwingConstants;

import fr.x9c.cadmium.kernel.ByteCodeParameters;

/**
 * This class implements a dialog for interpreter settings.
 *
 * @author <a href="mailto:cadmium@x9c.fr">Xavier Clerc</a>
 * @version 1.1
 * @since 1.0
 */
public final class ParametersDialog extends JDialog {

    /** Serialization UID. */
    static final long serialVersionUID = 306589335835953L;

    /** Title for dialog. */
    private static final String TITLE = "Interpreter parameters";

    /** Size of dialog borders. */
    private static final int BORDER = 16;

    /** Label for initial stack size. */
    private static final String INIT_STACK_SIZE = "Initial stack size:";

    /** Label for maximum stack size. */
    private static final String MAX_STACK_SIZE = "Maximum stack size:";

    /** Label for backtrace. */
    private static final String BACKTRACE = "Backtrace state:";

    /** Label for exit action. */
    private static final String EXIT = "Action upon exit:";

    /** Label for primitive providers. */
    private static final String PROVIDERS = "Primitive providers:";

    /** Label for dispatchers. */
    private static final String DISPATCHERS = "Dispatchers:";

    /** Label for frame. */
    private static final String FRAME = "Graphics frame:";

    /** Label for sound. */
    private static final String SOUND = "Sound:";

    /** Label for Dbm. */
    private static final String DBM = "Dbm implementation:";

    /** Label for os. */
    private static final String OS = "OS:";

    /** Label for unix emulation. */
    private static final String UNIX = "Unix primitives:";

    /** Label for embedded mode. */
    private static final String EMBEDDED = "Embedded mode:";

    /** Label for embedded base. */
    private static final String EMBEDDED_BASE = "Embedded base:";

    /** Possible values for backtrace. */
    private static final String[] BACKTRACE_VALUES = new String[] {
        "disabled",
        "enabled"
    };

    /** Possible values for exit action. */
    private static final String[] EXIT_VALUES = new String[] {
        "nothing",
        "stop JVM"
    };

    /** Possible values for dispatchers. */
    private static final String[] DISPATCHERS_VALUES = new String[] {
        "compilation",
        "reflection"
    };

    /** Possible values for frame. */
    private static final String[] FRAME_VALUES = new String[] {
        "Swing",
        "AWT"
    };

    /** Possible values for sound. */
    private static final String[] SOUND_VALUES = new String[] {
        "beep",
        "javax.sound"
    };

    /** Possible values for dbm. */
    private static final String[] DBM_VALUES = new String[] {
        "basic",
        "jdbm"
    };

    /** Possible values for os. */
    private static final String[] OS_VALUES = new String[] {
        "Unix",
        "Cygwin",
        "Win32",
        "MacOS",
        "Cadmium"
    };

    /** Possible values for unix emulation. */
    private static final String[] UNIX_VALUES = new String[] {
        "not implemented",
        "emulated"
    };

    /** Possible values for embedded mode. */
    private static final String[] EMBEDDED_VALUES = new String[] {
        "disabled",
        "enabled"
    };

    /** Initial stack size. */
    private final SpinnerNumberModel initialStackSize;

    /** Maximum stack size. */
    private final SpinnerNumberModel maxStackSize;

    /** Whether backtrace is activated. */
    private final JComboBox backtrace;

    /** Whether to stop JVM on exit. */
    private final JComboBox exit;

    /** Additional primitive providers. */
    private final JTextField providers;

    /** Whether dispatchers should be compiled. */
    private final JComboBox dispatchers;

    /** Whether to use AWT. */
    private final JComboBox frame;

    /** Whether to use <tt>javax.sound</tt>. */
    private final JComboBox sound;

    /** Whether to use <i>Dbm</i>. */
    private final JComboBox dbm;

    /** Underlying OS. */
    private final JComboBox os;

    /** Whether unix is emulated. */
    private final JComboBox unix;

    /** Whether embedded mode is activated. */
    private final JComboBox embedded;

    /** Base for embedded mode. */
    private final JTextField embeddedBase;

    /**
     * Constructs a dialog for parameters setting.
     * @param bcp initial values of parameters - should not be <tt>null</tt>
     */
    private ParametersDialog(final ByteCodeParameters bcp) {
        super((Frame) null, ParametersDialog.TITLE, true);
        assert bcp != null : "null bcp";

        // convert providers array to string
        final String[] prov = bcp.getProviders();
        final StringBuilder sb = new StringBuilder();
        final int len = prov.length;
        for (int i = 0; i < len; i++) {
            if (i > 0) {
                sb.append(',');
            } // end if
            sb.append(prov[i]);
        } // end for

        // computes osIndex
        int osIndex = 0;
        final String osValue = bcp.getOS();
        while ((osIndex < ParametersDialog.OS_VALUES.length)
               && !osValue.equals(ParametersDialog.OS_VALUES[osIndex])) {
            osIndex++;
        } // end while
        if (osIndex == ParametersDialog.OS_VALUES.length) {
            osIndex = 0;
        } // end if

        // parameters
        this.initialStackSize = new SpinnerNumberModel(bcp.getInitStackSize(),
                                                       512,
                                                       1024 * 1024,
                                                       1);
        this.maxStackSize = new SpinnerNumberModel(bcp.getMaxStackSize(),
                                                   512,
                                                   1024 * 1024,
                                                   1);
        this.backtrace = new JComboBox(ParametersDialog.BACKTRACE_VALUES);
        this.backtrace.setSelectedIndex(bcp.isBacktraceRequested() ? 1 : 0);
        this.exit = new JComboBox(ParametersDialog.EXIT_VALUES);
        this.exit.setSelectedIndex(bcp.isExitStoppingJVM() ? 1 : 0);
        this.providers = new JTextField(sb.toString());
        this.dispatchers = new JComboBox(ParametersDialog.DISPATCHERS_VALUES);
        this.dispatchers.setSelectedIndex(bcp.isDispatcherCompiled() ? 0 : 1);
        this.frame = new JComboBox(ParametersDialog.FRAME_VALUES);
        this.frame.setSelectedIndex(bcp.isFrameAWT() ? 1 : 0);
        this.sound = new JComboBox(ParametersDialog.SOUND_VALUES);
        this.sound.setSelectedIndex(bcp.isJavaxSoundUsed() ? 1 : 0);
        this.dbm = new JComboBox(ParametersDialog.DBM_VALUES);
        this.dbm.setSelectedIndex(bcp.isJDBMUsed() ? 1 : 0);
        this.os = new JComboBox(ParametersDialog.OS_VALUES);
        this.os.setSelectedIndex(osIndex);
        this.unix = new JComboBox(ParametersDialog.UNIX_VALUES);
        this.unix.setSelectedIndex(bcp.isUnixEmulated() ? 1 : 0);
        this.embedded = new JComboBox(ParametersDialog.EMBEDDED_VALUES);
        this.embedded.setSelectedIndex(bcp.isEmbedded() ? 1 : 0);
        final String embBase = bcp.getEmbeddedBase();
        this.embeddedBase = new JTextField(embBase != null ? embBase : "");

        // parameters panel
        final JPanel parametersPanel = new JPanel(new GridLayout(13, 2, 4, 4));
        parametersPanel.add(new JLabel(ParametersDialog.INIT_STACK_SIZE, SwingConstants.RIGHT));
        parametersPanel.add(new JSpinner(this.initialStackSize));
        parametersPanel.add(new JLabel(ParametersDialog.MAX_STACK_SIZE, SwingConstants.RIGHT));
        parametersPanel.add(new JSpinner(this.maxStackSize));
        parametersPanel.add(new JLabel(ParametersDialog.BACKTRACE, SwingConstants.RIGHT));
        parametersPanel.add(this.backtrace);
        parametersPanel.add(new JLabel(ParametersDialog.EXIT, SwingConstants.RIGHT));
        parametersPanel.add(this.exit);
        parametersPanel.add(new JLabel(ParametersDialog.PROVIDERS, SwingConstants.RIGHT));
        parametersPanel.add(this.providers);
        parametersPanel.add(new JLabel(ParametersDialog.DISPATCHERS, SwingConstants.RIGHT));
        parametersPanel.add(this.dispatchers);
        parametersPanel.add(new JLabel(ParametersDialog.FRAME, SwingConstants.RIGHT));
        parametersPanel.add(this.frame);
        parametersPanel.add(new JLabel(ParametersDialog.SOUND, SwingConstants.RIGHT));
        parametersPanel.add(this.sound);
        parametersPanel.add(new JLabel(ParametersDialog.DBM, SwingConstants.RIGHT));
        parametersPanel.add(this.dbm);
        parametersPanel.add(new JLabel(ParametersDialog.OS, SwingConstants.RIGHT));
        parametersPanel.add(this.os);
        parametersPanel.add(new JLabel(ParametersDialog.UNIX, SwingConstants.RIGHT));
        parametersPanel.add(this.unix);
        parametersPanel.add(new JLabel(ParametersDialog.EMBEDDED, SwingConstants.RIGHT));
        parametersPanel.add(this.embedded);
        parametersPanel.add(new JLabel(ParametersDialog.EMBEDDED_BASE, SwingConstants.RIGHT));
        parametersPanel.add(this.embeddedBase);

        // button panel
        final JPanel buttonPanel = new JPanel(new BorderLayout());
        final JButton okButton = new JButton("OK");
        okButton.addActionListener(new ActionListener() {
                public void actionPerformed(final ActionEvent ae) {
                    ParametersDialog.this.setVisible(false);
                } // end method 'actiopnPerformed(ActionEvent)'
            });
        buttonPanel.add(Box.createHorizontalGlue(), BorderLayout.CENTER);
        buttonPanel.add(okButton, BorderLayout.EAST);

        // main panel
        final JPanel mainPanel = new JPanel(new BorderLayout());
        mainPanel.add(parametersPanel, BorderLayout.CENTER);
        mainPanel.add(buttonPanel, BorderLayout.SOUTH);

        // content pane
        final Container cont = getContentPane();
        cont.add(Box.createVerticalStrut(ParametersDialog.BORDER), BorderLayout.NORTH);
        cont.add(Box.createVerticalStrut(ParametersDialog.BORDER), BorderLayout.SOUTH);
        cont.add(Box.createHorizontalStrut(ParametersDialog.BORDER), BorderLayout.EAST);
        cont.add(Box.createHorizontalStrut(ParametersDialog.BORDER), BorderLayout.WEST);
        cont.add(mainPanel, BorderLayout.CENTER);
        pack();
        setResizable(false);
    } // end constructor(ByteCodeParameters)

    /**
     * Constructs and opens a modals dialog for parameters setting. <br/>
     * @param bcp initial values of parameters - should not be <tt>null</tt>
     * @return parameters settings chosen by user
     */
    public static ByteCodeParameters open(final ByteCodeParameters bcp) {
        assert bcp != null : "null bcp";
        final ParametersDialog dlg = new ParametersDialog(bcp);
        dlg.setVisible(true);
        final String providers = dlg.providers.getText().trim();
        return new ByteCodeParameters(bcp.getArguments(),
                                      dlg.backtrace.getSelectedIndex() == 1,
                                      dlg.exit.getSelectedIndex() == 1,
                                      bcp.getStandardInput(),
                                      bcp.getStandardOutput(),
                                      bcp.getStandardError(),
                                      dlg.frame.getSelectedIndex() == 1,
                                      bcp.isBareCanvas(),
                                      dlg.sound.getSelectedIndex() == 1,
                                      dlg.dbm.getSelectedIndex() == 1,
                                      ParametersDialog.OS_VALUES[dlg.os.getSelectedIndex()],
                                      dlg.unix.getSelectedIndex() == 1,
                                      bcp.getFile(),
                                      bcp.isEmbedded(),
                                      bcp.getEmbeddedBase(),
                                      bcp.showDialog(),
                                      bcp.showHelp(),
                                      bcp.showVersion(),
                                      bcp.showDisassembled(),
                                      dlg.initialStackSize.getNumber().intValue(),
                                      dlg.maxStackSize.getNumber().intValue(),
                                      providers.length() == 0
                                      ? new String[0]
                                      : providers.split(","),
                                      dlg.dispatchers.getSelectedIndex() == 0);
    } // end method 'open()'

} // end class 'ParametersDialog'
